package com.chong.activiti.service.impl;

/**
 * @author chong
 * @create 2020/6/4
 * Desc:
 */

import com.chong.activiti.model.Activiti;
import com.chong.activiti.model.ActivitiTask;
import com.chong.activiti.service.ActivitiService;
import com.chong.activiti.utils.ActUtils;
import com.chong.activiti.utils.ActivitiUtil;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricVariableInstance;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.OutputStream;
import java.util.*;

/**
 * RepositoryService:  流程仓库Service，用于管理流程仓库，例如：部署，删除，读取流程资源
 * <p>
 * IdentityService：身份Service，可以管理，查询用户，组之间的关系
 * <p>
 * RuntimeService：运行时Service，可以处理所有正在运行状态的流程实例，任务等
 * <p>
 * TaskService：任务Service，用于管理，查询任务，例如：签收，办理，指派等
 * <p>
 * HistoryService：历史Service，可以查询所有历史数据，例如：流程实例，任务，活动，变量，附件等
 * <p>
 * FormService：表单Service，用于读取和流程，任务相关的表单数据
 * <p>
 * ManagementService：引擎管理Service，和具体业务无关，主要是可以查询引擎配置，数据库，作业等
 * <p>
 * DynamicBpmnService：一个新增的服务，用于动态修改流程中的一些参数信息等，是引擎中的一个辅助的服务
 */
@Slf4j
@Service
public class ActivitiServiceImpl implements ActivitiService {

    private static final String PROCESS_DEFINE_KEY = "demo";
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private IdentityService identityService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ProcessEngine processEngine;
   /* @Resource
    ProcessEngineFactoryBean processEngineFactory;
*/

    /**
     * 获取仓库服务 ：管理流程定义  流引擎执行操作
     *
     * @param resource     `act_re_deployment`表插入部署信息
     * @param name
     * @param categoryName
     */
    @Override
    public void deploy(String resource, String name, String categoryName) {
        //获取仓库服务 ：管理流程定义
        RepositoryService repositoryService = processEngine.getRepositoryService();
        Deployment deploy = repositoryService.createDeployment()//创建一个部署的构建器
                .addClasspathResource(resource)//从类路径中添加资源,一次只能添加一个资源
                .name(name)//设置部署的名称
                .category(categoryName)//设置部署的类别
                .deploy();
        log.info("部署的id" + deploy.getId());
        log.info("部署的名称" + deploy.getName());
    }

    @Override
    public Task initiationProcess() {
        log.info("method startActivityDemo begin....");
        log.info("调用流程存储服务，已部署流程数量："
                + repositoryService.createDeploymentQuery().count());
        Map<String, Object> map = new HashMap<>();
        // 流程图里写的${user} ，这里传进去user
        map.put("user", "主管王");
        map.put("approve", "人事李");
        //流程启动
        identityService.setAuthenticatedUserId("王充");    // 指定流程的发起者 不指定发起者的字段就为空，注意跟审核人分开
        ExecutionEntity pi = (ExecutionEntity) runtimeService.startProcessInstanceByKey("demo", map);
        System.out.println("启动流程成功！");

        Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        System.out.println("任务ID: " + task.getId());
        System.out.println("任务的办理人: " + task.getAssignee());
        System.out.println("任务名称: " + task.getName());
        System.out.println("任务的创建时间: " + task.getCreateTime());
        System.out.println("流程实例ID: " + task.getProcessInstanceId());

       /* Map<String,Object> map2 = new HashMap<>();
        map2.put("user","人事李");
        taskService.complete(task.getId(),map2);  // 开启后，环节会走到发起请假请求，要完成这个环节，才能到下一个审核环节
        System.out.println("method startActivityDemo end....");*/
        return task;
    }

    /**
     * 开始流程
     *
     * @param vac
     * @return
     */
    @Override
    public Task startActiviti(Activiti vac, String tempName, Map<String, Object> map) {
        log.info("method startActivityDemo begin....");
    /*
    认证用户的作用是设置流程发起人：在流程开始之前设置，会自动在表ACT_HI_PROCINST 中的START_USER_ID_中设置用户ID
    用来设置启动流程的人员ID，引擎会自动把用户ID保存到activiti:initiator中*/
        ProcessInstance pi = getTask(vac.getAuditor(), tempName, map);
        Task currentTask = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        // 申明任务人
        //taskService.claim(currentTask.getId(), userName);
        taskService.setAssignee(currentTask.getId(), vac.getAuditor());
        Map<String, Object> vars = new HashMap<>();
        vars.put("applyUser", vac.getApplyUser());
        vars.put("days", vac.getDays());
        vars.put("applyTime", new Date());
        vars.put("reason", vac.getReason());
        vars.put("applyStatus", 1);
        //在此方法中，Vaction 是申请时的具体信息，在完成“申请请假”任务时，可以将这些信息设置成参数。
        //完成第一步申请
        taskService.complete(currentTask.getId(), vars);
        return currentTask;
    }

    /**
     * 启动流程
     * @param perid
     * @param tempName
     * @param map
     * @return
     */
    @Override
    public ProcessInstance getTask(String perid, String tempName, Map<String, Object> map) {
        identityService.setAuthenticatedUserId(perid);
        // 开始流程  map内容set对应的审核用户
        ProcessInstance pi = runtimeService.startProcessInstanceByKey(tempName, map);
        // 查询当前任务
        return pi;
    }


    @Override
    public List<Activiti> myActiviti(String userName) {
        List<ProcessInstance> instanceList = runtimeService.createProcessInstanceQuery().startedBy(userName).list();
        List<Activiti> activitisList = new ArrayList<>();
        for (ProcessInstance instance : instanceList) {
            Activiti activiti = getActiviti(instance);
            activitisList.add(activiti);
        }
        return activitisList;
    }


    /**
     * 查询需要自己审批
     *
     * @param userName
     * @return
     */
    @Override
    public List<ActivitiTask> myApproval(String userName) {
        List<Task> taskList = taskService.createTaskQuery().taskAssignee(userName)
                .orderByTaskCreateTime().desc().list();
        // 多此一举 taskList中包含了以下内容(用户的任务中包含了所在用户组的任务)
        //        Group group = identityService.createGroupQuery().groupMember(userName).singleResult();
        //        List<Task> list = taskService.createTaskQuery().taskCandidateGroup(group.getId()).list();
        //        taskList.addAll(list);
        List<ActivitiTask> activitiTaskList = new ArrayList<>();
        for (Task task : taskList) {
            ActivitiTask activitiTask = new ActivitiTask();
            activitiTask.setId(task.getId());
            activitiTask.setName(task.getName());
            activitiTask.setCreateTime(task.getCreateTime());
            String instanceId = task.getProcessInstanceId();
            ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
            Activiti activiti = getActiviti(instance);
            activitiTask.setActiviti(activiti);
            activitiTask.setProcessInstanceId(instanceId);
            activitiTaskList.add(activitiTask);
        }
        return activitiTaskList;
    }


    private Activiti getActiviti(ProcessInstance instance) {
        Integer days = runtimeService.getVariable(instance.getId(), "days", Integer.class);
        String reason = runtimeService.getVariable(instance.getId(), "reason", String.class);
        Activiti activiti = new Activiti();
        activiti.setApplyUser(instance.getStartUserId());
        activiti.setDays(days);
        activiti.setReason(reason);
        Date startTime = instance.getStartTime(); // activiti 6 才有
        activiti.setApplyTime(startTime);
        activiti.setApplyStatus(instance.isEnded() ? "申请结束" : "等待审批");
        return activiti;
    }


/**
 * Activiti任务认领
 * taskService.setAssignee(String taskId, String userId);
 * taskService.claim(String taskId, String userId);
 * taskService.setOwner(String taskId, String userId);
 * setAssignee和claim两个的区别是在认领任务时，claim会检查该任务是否已经被认领，如果被认领则会抛出ActivitiTaskAlreadyClaimedException ,而setAssignee不会进行这样的检查，其他方面两个方法效果一致。
 * setOwner和setAssignee的区别在于,setOwner是在代理任务时使用，代表着任务的归属者，而这时，setAssignee代表的是代理办理者，
 *
 * 举个例子来说，公司总经理现在有个任务taskA，去核实一下本年度的财务报表，他现在又很忙没时间，于是将该任务委托给其助理进行办理，此时，就应该这么做
 * taskService.setOwner(taskA.getId(), 总经理.getId());
 * taskService.setAssignee/claim(taskA.getId(), 助理.getId());
 */
    /**
     * 审批操作
     *
     * @param userName
     * @param activitiTask 同理，result是审批的结果，也是在完成审批任务时需要传入的参数；taskId是刚才老板查询到的当前需要自己完成的审批任务ID。
     *                     （如果流程在这里设置分支，可以通过判断result的值来跳转到不同的任务）
     * @return
     */
    @Override
    public Boolean passApproval(String userName, ActivitiTask activitiTask) {
        String taskId = activitiTask.getId();
        Map<String, Object> vars = new HashMap<>();
        vars.put("result", activitiTask.getActiviti().getResult());
        vars.put("auditor", activitiTask.getActiviti().getAuditor());
        vars.put("auditTime", new Date());
        vars.put("ifPass", activitiTask.getActiviti().getIfPass());
        //taskService.claim(taskId, userName);
        taskService.setAssignee(taskId, userName);
        taskService.complete(taskId, vars);
        return true;
    }


    /**
     * 查询已完成的请假记录
     * 由于已完成的请假在数据库runtime表中查不到（runtime表只保存正在进行的流程示例信息），所以需要在history表中查询。
     *
     * @param userName
     * @return
     */
    @Override
    public List<Activiti> myActivitiRecord(String userName) {
        List<HistoricProcessInstance> hisProInstance = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey(PROCESS_DEFINE_KEY).startedBy(userName).finished()
                .orderByProcessInstanceEndTime().desc().list();

        List<Activiti> activitiList = new ArrayList<>();
        for (HistoricProcessInstance hisInstance : hisProInstance) {
            Activiti activiti = new Activiti();
            activiti.setApplyUser(hisInstance.getStartUserId());
            activiti.setApplyTime(hisInstance.getStartTime());
            activiti.setApplyStatus("申请结束");
            List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(hisInstance.getId()).list();
            ActivitiUtil.setVars(activiti, varInstanceList);
            activitiList.add(activiti);
        }
        return activitiList;
    }


    /**
     * 我审批的记录列表
     *
     * @param userName
     * @return
     */
    @Override
    public List<Activiti> myApprovalRecord(String userName) {
        List<HistoricProcessInstance> hisProInstance = historyService.createHistoricProcessInstanceQuery()
                .processDefinitionKey(PROCESS_DEFINE_KEY).involvedUser(userName).finished()
                .orderByProcessInstanceEndTime().desc().list();
        List<Activiti> activitiList = new ArrayList<Activiti>();
        for (HistoricProcessInstance hisInstance : hisProInstance) {
        /*List<HistoricTaskInstance> hisTaskInstanceList = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(hisInstance.getId()).processFinished()
                .taskAssignee(userName)
                .taskNameIn(auditTaskNameList)
                .orderByHistoricTaskInstanceEndTime().desc().list();
        boolean isMyAudit = false;
        for (HistoricTaskInstance taskInstance : hisTaskInstanceList) {
            if (taskInstance.getAssignee().equals(userName)) {
                isMyAudit = true;
            }
        }
        if (!isMyAudit) {
            continue;
        }*/
            Activiti activiti = new Activiti();
            activiti.setApplyUser(hisInstance.getStartUserId());
            activiti.setApplyStatus("申请结束");
            activiti.setApplyTime(hisInstance.getStartTime());
            List<HistoricVariableInstance> varInstanceList = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(hisInstance.getId()).list();
            ActivitiUtil.setVars(activiti, varInstanceList);
            activitiList.add(activiti);
        }
        return activitiList;
    }

    /**
     * 生成流程图片
     *
     * @param processDefinitionId
     * @param outputStream
     * @return
     */
    @Override
    public void tracePhoto(String processDefinitionId, OutputStream outputStream) {
        ActUtils.getFlowImgByInstanceId(processDefinitionId, outputStream);
    }
}
